package main

import (
	"fmt"
	"os"
	"path/filepath"
	"rvld/src/linker"
	"rvld/src/utils"
	"strings"
)

func parseArgs(ctx *linker.Context) []string {
	args := os.Args[1:]
	dashes := func(name string) []string {
		if len(name) == 1 {
			return []string{"=" + name}
		}

		return []string{"-" + name, "--" + name}
	}

	oneArg := ""
	readArg := func(name string) bool {
		for _, opt := range dashes(name) {
			if args[0] == opt {
				if len(args) == 1 {
					utils.ThisFatal(fmt.Sprintf("option -%s: argument missing", name))
				}

				oneArg = args[1]
				args = args[2:]
				return true
			}

			prefix := opt
			if len(name) > 1 {
				prefix += "="
			}

			if strings.HasPrefix(args[0], prefix) {
				oneArg = args[0][len(prefix):]
				args = args[1:]
				return true
			}
		}

		return false
	}

	readFlag := func(name string) bool {
		for _, opt := range dashes(name) {
			if args[0] == opt {
				args = args[1:]
				return true
			}
		}
		return false
	}

	remaining := make([]string, 0)
	// default print help if not provide args
	if len(args) == 0 {
		fmt.Printf("usage: %s [options] file...\n", os.Args[0])
		os.Exit(0)
	}

	for len(args) > 0 {
		if readFlag("help") {
			fmt.Printf("usage: %s [options] file...\n", os.Args[0])
			os.Exit(0)
		}

		if readArg("o") || readArg("output") {
			ctx.Args.Output = oneArg
		} else if readFlag("v") || readFlag("version") {
			fmt.Printf("%s %s\n", os.Args[0], linker.AppVersion)
			os.Exit(0)
		} else if readArg("m") {
			if oneArg == "elf64riscv" {
				ctx.Args.Emulation = linker.MachineTypeRISCV64
			} else {
				utils.ThisFatal(fmt.Sprintf("Unknown or unsupported argument %s for -m", oneArg))
			}
		} else if readArg("L") {
			ctx.Args.LibraryPaths = append(ctx.Args.LibraryPaths, oneArg)
		} else if readArg("l") {
			remaining = append(remaining, "-l"+oneArg) // link to the specified lib
		} else if readArg("sysroot") ||
			readArg("plugin") ||
			readArg("plugin-opt") ||
			readFlag("as-needed") ||
			readFlag("start-group") ||
			readFlag("end-group") ||
			readArg("hash-style") ||
			readArg("build-id") ||
			readFlag("s") ||
			readFlag("no-relax") {
			fmt.Printf("currently not support %s, ignore it now\n", oneArg)
			os.Exit(0)
		} else {
			if args[0][0] == '-' {
				utils.ThisFatal(fmt.Sprintf("unknown option: %s\n", args[0]))
				os.Exit(1)
			}
			remaining = append(remaining, args[0])
			args = args[1:]
		}
	}

	for i, path := range ctx.Args.LibraryPaths {
		ctx.Args.LibraryPaths[i] = filepath.Clean(path)
	}

	return remaining
}

// Entry Point
func main() {
	ctx := linker.NewContext()
	remaining := parseArgs(ctx)

	if ctx.Args.Emulation == linker.MachineTypeNone {
		for _, filename := range remaining {
			if strings.HasPrefix(filename, "-") {
				continue
			}

			file := linker.MustNewFile(filename)
			ctx.Args.Emulation = linker.GetMachineTypeFromContents(file.Contents)
			if ctx.Args.Emulation != linker.MachineTypeNone {
				break
			}
		}
	}

	if ctx.Args.Emulation != linker.MachineTypeRISCV64 {
		utils.ThisFatal(fmt.Sprintf("unknown emulation type, the provided machine type: %s", string(ctx.Args.Emulation)))
	}

	linker.ReadInputFiles(ctx, remaining)
	linker.ResolveSymbols(ctx)
	linker.RegisterSectionPieces(ctx)
	linker.ComputeMergedSectionSizes(ctx)
	linker.CreateSyntheticSections(ctx)
	linker.BinSections(ctx)
	ctx.Chunks = append(ctx.Chunks, linker.CollectOutputSections(ctx)...)
	linker.ScanRelocations(ctx)
	linker.ComputeSectionSizes(ctx)
	linker.SortOutputSections(ctx)

	for _, chunk := range ctx.Chunks {
		chunk.UpdateShdr(ctx)
	}

	fileSize := linker.SetOutputSectionOffsets(ctx)

	ctx.Buf = make([]byte, fileSize)
	file, err := os.OpenFile(ctx.Args.Output, os.O_RDWR|os.O_CREATE, 0777)
	utils.ThisMustNo(err)
	for _, chunk := range ctx.Chunks {
		chunk.CopyBuf(ctx)
	}

	_, err = file.Write(ctx.Buf)
	utils.ThisMustNo(err)
}
